﻿namespace LiveLabs.Logging
{
    using System;
    using System.Collections.Generic;
    using Xunit;
    using Xunit.Extensions;

    public class LoggerFixture
    {
        private TestLog log;
        private Logger logger;

        public LoggerFixture()
        {
            log = new TestLog();

            logger = new Logger(typeof(Foo<DateTime, TimeSpan>.Inner), log);
            logger.Level = LogLevel.Fatal;
            logger.MessageFormatter = (message, n, l) => message;
            logger.ExceptionFormatter = (ex, n, l) => ex.Message;
        }

        public static IEnumerable<object[]> LevelAndExpectation
        {
            get
            {
                yield return new object[] { LogLevel.Fatal, 1 };
                yield return new object[] { LogLevel.Error, 2 };
                yield return new object[] { LogLevel.Warn, 3 };
                yield return new object[] { LogLevel.Inform, 4 };
                yield return new object[] { LogLevel.Debug, 5 };
                yield return new object[] { LogLevel.Verbose, 6 };
            }
        }

        [Theory]
        [PropertyData("LevelAndExpectation")]
        public void Should_Not_Log_At_Lower_Level_Using_Explicit_Log(LogLevel setTo, int expectedEntries)
        {
            LogLevel[] all = new LogLevel[] {
                LogLevel.Fatal,
                LogLevel.Error,
                LogLevel.Warn,
                LogLevel.Inform,
                LogLevel.Debug,
                LogLevel.Verbose
            };

            logger.Level = setTo;

            foreach (LogLevel level in all)
            {
                logger.Log("hello", level);
            }

            Assert.Equal(expectedEntries, log.Entries.Count);
        }

        [Theory]
        [PropertyData("LevelAndExpectation")]
        public void Should_Not_Log_At_Lower_Level_Using_Implicit_Log(LogLevel setTo, int expectedEntries)
        {
            Action<string>[] logMethods = new Action<string>[] {
                logger.Fatal,
                logger.Error,
                logger.Warn,
                logger.Inform,
                logger.Debug,
                logger.Verbose
            };

            logger.Level = setTo;

            foreach (Action<string> method in logMethods)
            {
                method("hello");
            }

            Assert.Equal(expectedEntries, log.Entries.Count);
        }

        [Theory]
        [PropertyData("LevelAndExpectation")]
        public void Should_Not_Execute_Message_Generator_Delegate_Using_Explicit_Log(LogLevel setTo, int expectedExecutions)
        {
            int actualExecutions = 0;
            MessageGenerator generator = () => (++actualExecutions).ToString();

            LogLevel[] all = new LogLevel[] {
                LogLevel.Fatal,
                LogLevel.Error,
                LogLevel.Warn,
                LogLevel.Inform,
                LogLevel.Debug,
                LogLevel.Verbose
            };

            logger.Level = setTo;

            foreach (LogLevel level in all)
            {
                logger.Log(generator, level);
            }

            Assert.Equal(expectedExecutions, actualExecutions);
            Assert.Equal(expectedExecutions, log.Entries.Count);
        }

        [Theory]
        [PropertyData("LevelAndExpectation")]
        public void Should_Not_Execute_Message_Generator_Delegate_Using_Implicit_Log(LogLevel setTo, int expectedExecutions)
        {
            int actualExecutions = 0;
            MessageGenerator generator = () => (++actualExecutions).ToString();

            Action<MessageGenerator>[] logMethods = new Action<MessageGenerator>[] {
                logger.Fatal,
                logger.Error,
                logger.Warn,
                logger.Inform,
                logger.Debug,
                logger.Verbose
            };

            logger.Level = setTo;

            foreach (Action<MessageGenerator> method in logMethods)
            {
                method(generator);
            }

            Assert.Equal(expectedExecutions, actualExecutions);
            Assert.Equal(expectedExecutions, log.Entries.Count);
        }

        [Fact]
        public void Should_Execute_Message_Formatter_Delegate()
        {
            bool executed = false;

            // catch that we execute the formatter
            logger.MessageFormatter = (m, n, l) => (executed = true).ToString();

            // set our level really low...
            logger.Level = LogLevel.Verbose;

            // log a message
            logger.Log("hello", LogLevel.Inform);

            Assert.True(executed);
        }

        [Fact]
        public void Should_Execute_Exception_Formatter_Delegate()
        {
            bool executed = false;

            // catch that we execute the formatter
            logger.ExceptionFormatter = (m, n, l) => (executed = true).ToString();

            // set our level really low...
            logger.Level = LogLevel.Verbose;

            try
            {
                throw new Exception("hello");
            }
            catch (Exception ex)
            {
                logger.Log(ex, LogLevel.Error);
            }

            Assert.True(executed);
        }

        [Fact]
        public void Should_Pull_Settings_From_Settings_Instance()
        {
            MessageFormatter msgFormat = (m, n, l) => m;
            ExceptionFormatter exFormat = (ex, n, l) => ex.Message;
            Log log = new TestLog();

            Settings set = new Settings();
            set.RegisterLogLevelFor<Foo<DateTime, TimeSpan>.Inner>(LogLevel.Debug);
            set.RegisterLogFor<Foo<DateTime, TimeSpan>.Inner>(log);
            set.RegisterMessageFormatterFor<Foo<DateTime, TimeSpan>.Inner>(msgFormat);
            set.RegisterExceptionFormatterFor<Foo<DateTime, TimeSpan>.Inner>(exFormat);

            Logger l1 = new Logger(typeof(Foo<DateTime, TimeSpan>.Inner), set);
            Assert.Equal(LogLevel.Debug, l1.Level);
            Assert.Same(log, l1.DestinationLog);
            Assert.Same(msgFormat, l1.MessageFormatter);
            Assert.Same(exFormat, l1.ExceptionFormatter);

            // since this is a child of the one we configured above, it should pick up
            // the same settings (notice foo has different type params)
            l1 = new Logger(typeof(Foo<DateTimeOffset, double>.Inner.Inner2<string>), set);
            Assert.Equal(LogLevel.Debug, l1.Level);
            Assert.Same(log, l1.DestinationLog);
            Assert.Same(msgFormat, l1.MessageFormatter);
            Assert.Same(exFormat, l1.ExceptionFormatter);
        }
    }
}
